/*TODO: use QThread's somewhere in here?  At least for the surface area and volume calculations?
 * Can profile to see how the performance goes; research how to determine optimal number of threads
 */

/*TODO: calculate matrices etc only when there's a change (not once per frame)
 *
 */

/* TODO: verify qDebug(), really is a no-op for release build:
 * This function does nothing if QT_NO_DEBUG_OUTPUT was defined during compilation
 * Also, even for DEBUG build, we could be more selective about qDebug() prints, or have multiple levels of verbosity
 */

/* TODO: What version for GLSL ES with OpenGL ES 2.0?
 *
 * http://www.khronos.org/registry/gles/#specs2 -> http://www.khronos.org/registry/gles/specs/2.0/es_full_spec_2.0.25.pdf :
 * OpenGL ES 2.0 implementations are guaranteed to support at least version 1.0 of the shading language; the actual version supported may be queried as described in section 6.1.5.
 *
 * http://www.khronos.org/registry/gles/specs/2.0/GLSL_ES_Specification_1.0.17.pdf
 *
 * https://stackoverflow.com/questions/23853345/qt-desktop-shader-compilation-issue
 * Any number representing a version of the language a compiler does not support will cause a compile-time error to be generated.
 * Version 1.10 of the language does not require shaders to include this directive, and shaders that do not include a #version
 * directive will be treated as targeting version 1.10. Shaders that specify #version 100 will be treated as targeting version 1.00
 * of the OpenGL ES Shading Language.
 *
 * The problem is this - OpenGL ES 2.0 spec says to use (#version 100) but that gives a compilation error when I ran on desktop (I tried OS X and an old version of Ubuntu)
 * If I don't declare any #version, then it works...  But...  What would work best on the widest variety of platforms including older computers?
 * To what extent does Qt's OpenGL really handle this?
 * Is there significant benefit to using a newer shader version when the host supports it?  In either case, the first goal might be to focus on a minimum older shader version
 */

/* TODO: review the init() mess
 */

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QtOpenGL>
#include <QFile>
#include <limits>
#include <QHash>
//#include <QMultiHash>

#define M_PIf (float)M_PI

Entity::Entity()
{
}

Entity::~Entity()
{
}

StlEntity::StlEntity()
{
	m_OffX = 0.0f;
	m_OffY = 0.0f;
	m_OffZ = 0.0f;
	m_PosX = 0.0f;
	m_PosY = 0.0f;
	m_PosZ = 0.0f;
	m_RotX = 0.0f;
	m_RotY = 0.0f;
	m_RotZ = 0.0f;
	m_Scale = 1.0f;

	m_SurfaceArea = 0.0f;
	m_Volume = 0.0f;
}

StlEntity::~StlEntity()
{
}

void StlEntity::init()
{
	QOpenGLShader programVS(QOpenGLShader::Vertex);
	programVS.compileSourceFile("../pemSTL/shaders/simple.vs");
	QOpenGLShader programFS(QOpenGLShader::Fragment);
	programFS.compileSourceFile("../pemSTL/shaders/simple.fs");
	m_Program.addShader(&programVS);
	m_Program.addShader(&programFS);
	m_Program.link();
	m_Program.bind();
}

void StlEntity::draw(QMatrix4x4 &viewMat, QMatrix4x4 &projMat, QVector3D &lightPos)
{
	m_Program.bind();
	m_Program.setAttributeArray("pos", m_Positions.constData());
	m_Program.enableAttributeArray("pos");
	m_Program.setAttributeArray("norm", m_Normals.constData());
	m_Program.enableAttributeArray("norm");

	QVector3D rightVec = QVector3D(1,0,0);
	QVector3D upVec = QVector3D(0,1,0);
	QVector3D frontVec = QVector3D(0,0,1);

	m_MatModel.setToIdentity();
	m_MatModel.translate(m_OffX, m_OffY, m_OffZ);
	m_MatModel.rotate(qRadiansToDegrees(m_RotX), rightVec);
	m_MatModel.rotate(qRadiansToDegrees(m_RotY), upVec);
	m_MatModel.rotate(qRadiansToDegrees(m_RotZ), frontVec);
	m_MatModel.translate(m_PosX, m_PosY, m_PosZ);
	m_MatModel.scale(m_Scale);

	//TODO: camera should not be in StlEntity...  this is just a temporary hack for the incremental step of seeing one STL model draw
	//QMatrix4x4 matViewProj;
	////matViewProj.ortho(-10, 10, -10, 10, -10, 10);
	//float width = m_XMax - m_XMin;
	//float height = m_YMax - m_YMin;
	//float depth = m_ZMax - m_ZMin;
	//matViewProj.ortho(-width/2, width/2, -height/2, height/2, -depth/2 - 30, depth/2); //TODO: for now, center ortho view on object
	//
	//TODO: change camera type
	//matViewProj = viewProj;

	// VS uniforms
	QMatrix4x4 modelViewProj = projMat * viewMat * m_MatModel;
	QMatrix4x4 modelView = viewMat * m_MatModel;
	QMatrix3x3 normalMatrix;
	normalMatrix = modelView.normalMatrix();
	m_Program.setUniformValue("modelViewProj", modelViewProj);
	m_Program.setUniformValue("modelView", modelView);
	m_Program.setUniformValue("normalMat", normalMatrix);
	m_Program.setUniformValue("lightPos", lightPos);
	m_Program.setUniformValue("model", m_MatModel);

	// FS uniforms
	m_Program.setUniformValue("ambientColor", QColor(160, 80, 0));
	m_Program.setUniformValue("diffuseColor", QColor(255, 128, 0));
	m_Program.setUniformValue("specularColor", QColor(255, 255, 255));
	m_Program.setUniformValue("ambientReflection", (GLfloat) 1.0);
	m_Program.setUniformValue("diffuseReflection", (GLfloat) 1.0);
	m_Program.setUniformValue("specularReflection", (GLfloat) 1.0);
	m_Program.setUniformValue("shininess", (GLfloat) 100.0);

	glDrawArrays(GL_TRIANGLES, 0, m_Positions.length());

	m_Program.disableAttributeArray("pos");
	m_Program.disableAttributeArray("norm");
	m_Program.release();
}

void calculateNormal(QVector3D &normal, QVector3D vec0, QVector3D vec1, QVector3D vec2)
{
	//float v1[3];
	//float v2[3];
	//v1[0] = vec1.x() - vec0.x();
	//v1[1] = vec1.y() - vec0.y();
	//v1[2] = vec1.z() - vec0.z();
	//v2[0] = vec2.x() - vec0.x();
	//v2[1] = vec2.y() - vec0.y();
	//v2[2] = vec2.z() - vec0.z();
	//normal.setX((float)((double)v1[1] * (double)v2[2]) - ((double)v1[2] * (double)v2[1]));
	//normal.setY((float)((double)v1[2] * (double)v2[0]) - ((double)v1[0] * (double)v2[2]));
	//normal.setZ((float)((double)v1[0] * (double)v2[1]) - ((double)v1[1] * (double)v2[0]));

	QVector3D vEdge10 = vec1 - vec0;
	QVector3D vEdge20 = vec2 - vec0;
	normal = QVector3D::crossProduct(vEdge10, vEdge20);
}

uint qHash(QVector3D key)
{
	union float_uint
	{
		uint ui;
		float f;

		float_uint(float val) { f = val; }
	};
	float_uint xUnion(key.x());
	float_uint yUnion(key.y());
	float_uint zUnion(key.x());
	return xUnion.ui ^ yUnion.ui ^ zUnion.ui;
}

//TODO: reconsider having GUI stuff here if we want to support a script/console or API mode?
void StlEntity::loadFromFile(QString sFileName)
{
	bool bNormalsFromFile = false;
	bool bNormalsSmooth = false;

	QFile file(sFileName);
	if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
	{
		QMessageBox msgBox;
		msgBox.setText("Error: could not open file " + file.fileName());
		msgBox.setStandardButtons(QMessageBox::Ok);
		msgBox.exec();
		return;
	}

	//QMultiHash<QVector3D, QVector3D> posToNorms;
	QHash<QVector3D, QVector3D> posToNorm;
	QTextStream in(&file);
	int iLineNum = 0; //TODO: does QTextStream or QFile track line number?
	while (!in.atEnd())
	{
		QString line = in.readLine();
		iLineNum++;

		QMessageBox msgBoxErr; // in case we do get an error
		QString strErr = "Error: parsing file " + file.fileName() + ", line " + iLineNum + ": ";
		QStringList lineSplit = line.split(" ", QString::SkipEmptyParts);
		msgBoxErr.setStandardButtons(QMessageBox::Ok);

		if (lineSplit[0] == "facet" && lineSplit[1] == "normal")
		{
			if (!bNormalsFromFile)
				continue;
			if (lineSplit.length() < 4)
			{
				msgBoxErr.setText(strErr + "facet: less than 3 values, defaulting to 0.0f");
				msgBoxErr.exec();
			}
			float xNorm = lineSplit[1].toFloat();
			float yNorm = lineSplit[2].toFloat();
			float zNorm = lineSplit[3].toFloat();
			//qDebug() << "facet normal: " << lineSplit[0] << " " << xNorm << " " << yNorm << " " << zNorm;
			m_Normals << QVector3D(xNorm, yNorm, zNorm);
			m_Normals << QVector3D(xNorm, yNorm, zNorm);
			m_Normals << QVector3D(xNorm, yNorm, zNorm);
		}
		else if(lineSplit[0] == "vertex")
		{
			if (lineSplit.length() < 4)
			{
				msgBoxErr.setText(strErr + "vertex: less than 3 values, defaulting to 0.0f");
				msgBoxErr.exec();
			}
			float xPos = lineSplit[1].toFloat();
			float yPos = lineSplit[2].toFloat();
			float zPos = lineSplit[3].toFloat();
			//qDebug() << "vertex: " << lineSplit[0] << " " << xPos << " " << yPos << " " << zPos;
			m_Positions << QVector3D(xPos, yPos, zPos);

			if (bNormalsFromFile)
				continue;
			if (m_Positions.size() %3 == 0 && m_Positions.size() != 0)
			{
				QVector3D normal;
				int lastIndex = m_Positions.size()-1;
				calculateNormal(normal, m_Positions[lastIndex], m_Positions[lastIndex-1], m_Positions[lastIndex-2]);

				// flat normals
				if (!bNormalsSmooth)
				{
					m_Normals << normal;
					m_Normals << normal;
					m_Normals << normal;
				}

				// smooth normals
				//
				// This post likes angle-weighted average:
				// http://meshlabstuff.blogspot.com/2009/04/on-computation-of-vertex-normals.html
				//
				// This paper compares methods and seems to like MWA
				// http://users.tricity.wsu.edu/~bobl/personal/mypubs/2003_vertnorm_tvc.pdf
				//
				// More ideas:
				// http://steve.hollasch.net/cgindex/geometry/surfnorm.html
				//
				// Great tutorial argues that angle weights are better than area weights:
				// http://www.bytehazard.com/articles/vertnorm.html
				//
				// More experiments (this guy thinks angle weights are worse) (though he doesn't explain):
				// http://epii.info/oss/blender/smooth/Calculating%20smooth%20vertex%20normals.pdf
				//
				// Smooth shading only for shallow angles:
				// https://www.youtube.com/watch?v=PMgjVJogIbc
				//
				//TODO: try angle-based weights
				else
				{
					// cross-product is proportional to area of triangle
					posToNorm[m_Positions[lastIndex-0]] += normal;
					posToNorm[m_Positions[lastIndex-1]] += normal;
					posToNorm[m_Positions[lastIndex-2]] += normal;

					// Another method described here:
					// https://computing.llnl.gov/vis/images/pdf/max_jgt99.pdf
					//
					// This code claims to use that method:
					// http://gfx.cs.princeton.edu/proj/trimesh2/src/trimesh2-2.11/libsrc/TriMesh_normals.cc
					//
					//vec a = p0-p1, b = p1-p2, c = p2-p0;
					//float l2a = len2(a), l2b = len2(b), l2c = len2(c);
					//if (!l2a || !l2b || !l2c)
					//	continue;
					//vec facenormal = a CROSS b;
					//normals[faces[i][0]] += facenormal * (1.0f / (l2a * l2c));
					//normals[faces[i][1]] += facenormal * (1.0f / (l2b * l2a));
					//normals[faces[i][2]] += facenormal * (1.0f / (l2c * l2b));
					//
					//QVector3D a = m_Positions[lastIndex-0] - m_Positions[lastIndex-1];
					//QVector3D b = m_Positions[lastIndex-1] - m_Positions[lastIndex-2];
					//QVector3D c = m_Positions[lastIndex-2] - m_Positions[lastIndex-0];
					//float l2a = a.length();
					//float l2b = b.length();
					//float l2c = c.length();
					//QVector3D facenormal = QVector3D::crossProduct(a, b);
					//posToNorm[m_Positions[lastIndex-0]] += facenormal * (1.0f / (l2a * l2c));
					//posToNorm[m_Positions[lastIndex-1]] += facenormal * (1.0f / (l2b * l2a));
					//posToNorm[m_Positions[lastIndex-2]] += facenormal * (1.0f / (l2c * l2b));
				}
			}
		}
		else if(lineSplit[0] == "solid" || lineSplit[0] == "outer" || lineSplit[0] == "endloop" || lineSplit[0] == "endfacet" || lineSplit[0] == "endsolid")
		{
		}
		else
		{
			msgBoxErr.setText(strErr + "unexpected start of line" );
		}
	}

	// smooth normals
	if (bNormalsSmooth)
	{
		for (int i = 0; i < m_Positions.size(); ++i)
		{
			QVector3D norm = posToNorm[m_Positions[i]];
			norm.normalize();
			m_Normals << norm;
		}
	}

	// find min max values
	m_XMin = m_YMin = m_ZMin = std::numeric_limits<float>::max();
	m_XMax = m_YMax = m_ZMax = std::numeric_limits<float>::min();
	for (QVector<QVector3D>::iterator it = m_Positions.begin(); it != m_Positions.end(); ++it)
	{
		m_XMin = std::min(m_XMin, it->x());
		m_YMin = std::min(m_YMin, it->y());
		m_ZMin = std::min(m_ZMin, it->z());
		m_XMax = std::max(m_XMax, it->x());
		m_YMax = std::max(m_YMax, it->y());
		m_ZMax = std::max(m_ZMax, it->z());
	}
	//qDebug() << m_XMin << "to" << m_XMax << " " << m_YMin << "to" << m_YMax << " " << m_ZMin << "to" << m_ZMax;

	this->calculateSurfaceArea();
	this->calculateVolume();

	// scale based on bounding box's max dimension
	float maxDimension = qMax(qMax(this->GetWidth(), this->GetHeight()), this->GetDepth());
	if (maxDimension > 25.0f)
	{
		m_Scale = 25.0f / maxDimension;
	}
	else if (maxDimension < 15.0f)
	{
		m_Scale = 15.0f / maxDimension;
	}

	// center scaled model at origin for X
	m_PosX = -(m_XMin+m_XMax)*m_Scale/2.0f;
	m_PosY = -(m_YMin+m_YMax)*m_Scale/2.0f;
	m_PosZ = -(m_ZMin+m_ZMax)*m_Scale/2.0f;

	// models drop to floor for Y
	float const FLOOR = -12.0f;
	m_OffY = -(m_YMin*m_Scale - FLOOR) - m_PosY;
}

void StlEntity::calculateSurfaceArea()
{
	m_SurfaceArea = 0.0f;
	for (int i = 2; i < m_Positions.size(); i+=3)
	{
		QVector3D u = m_Positions[i] - m_Positions[i-1];
		QVector3D v = m_Positions[i] - m_Positions[i-2];
		QVector3D w = QVector3D::crossProduct(u, v);
		m_SurfaceArea += 0.5f * w.length();
	}
}

float StlEntity::getSurfaceArea()
{
	return m_SurfaceArea;
}

// https://stackoverflow.com/questions/1406029/how-to-calculate-the-volume-of-a-3d-mesh-object-the-surface-of-which-is-made-up
// -> http://research.microsoft.com/en-us/um/people/chazhang/publications/icip01_ChaZhang.pdf
//
// http://n-e-r-v-o-u-s.com/blog/?p=4415
//
//TODO: test with more models
//TODO: understand the equation better (volume of tetrahedron), make sure the negative volume part works
float SignedVolumeOfTriangle(QVector3D p1, QVector3D p2, QVector3D p3)
{
	float v321 = p3.x()*p2.y()*p1.z();
	float v231 = p2.x()*p3.y()*p1.z();
	float v312 = p3.x()*p1.y()*p2.z();
	float v132 = p1.x()*p3.y()*p2.z();
	float v213 = p2.x()*p1.y()*p3.z();
	float v123 = p1.x()*p2.y()*p3.z();
	return (1.0f/6.0f)*(-v321 + v231 + v312 - v132 - v213 + v123);
}
void StlEntity::calculateVolume()
{
	// iterate through each triangle - sum signed tetrahedron volumes
	m_Volume = 0.0f;
	for (int i = 2; i < m_Positions.size(); i+=3)
	{
		m_Volume += SignedVolumeOfTriangle(m_Positions[i-2], m_Positions[i-1], m_Positions[i]);
	}
}

float StlEntity::getVolume()
{
	return m_Volume;
}

OrbitalCamera::OrbitalCamera()
{
	m_Radius = 75.0f;
	m_Theta = M_PIf*2.0f;
	m_Phi = M_PIf;
}

OrbitalCamera::~OrbitalCamera()
{
}

void OrbitalCamera::setLookAtPt(QVector3D lookAt)
{
	m_LookAtPt = lookAt;
}

void OrbitalCamera::translateLookAtPt(QVector3D delta)
{
	m_LookAtPt += delta;
}

// https://en.wikipedia.org/wiki/Spherical_coordinate_system#Cartesian_coordinates
// Cartesian coordinates may be retrieved from the spherical coordinates (radius r, inclination theta, azimuth phi),
// x = r*sin(theta)*cos(phi)
// y = r*sin(theta)*sin(phi)
// z = r*cos(theta)
//
//TODO: refactor to calc when it changes etc (don't do the calculations here)
//
//TODO: fix the camera...  ASSIMP's open3mod viewer has a similar orbit camera:
//code is C# XNA: http://msdn.microsoft.com/en-us/library/bb463938.aspx
//private void ReCreateViewMatrix()
//{
//	//Calculate the relative position of the camera
//	position = Vector3.Transform(Vector3.Backward, Matrix.CreateFromYawPitchRoll(yaw, pitch, 0));
//	//Convert the relative position to the absolute position
//	position *= zoom;
//	position += lookAt;
//
//	//Calculate a new viewmatrix
//	viewMatrix = Matrix.CreateLookAt(position, lookAt, Vector3.Up);
//	viewMatrixDirty = false;
//}
QMatrix4x4 &OrbitalCamera::getViewMat()
{
	m_ViewMat.setToIdentity();
	m_EyePos.setX(m_Radius * sin(m_Theta) * cos(m_Phi));
	m_EyePos.setY(m_Radius * sin(m_Theta) * sin(m_Phi));
	m_EyePos.setZ(m_Radius * cos(m_Theta));
	m_EyePos += m_LookAtPt;
	//qDebug() << eyePos << ", " << m_Theta << ", " << m_Phi << " ; " << m_Radius;
	//qDebug("(%10.4f,%10.4f,%10.4f) (%10.4f,%10.4f) (%10.4f)", eyePos.x(), eyePos.y(), eyePos.z(), m_Theta/M_PIf, m_Phi/M_PIf, m_Radius);
	QVector3D upVec = QVector3D(0,1,0);
	QVector3D frontVec = QVector3D(0,0,1);
	QVector3D rightVec = QVector3D(1,0,0);
	QVector3D leftVec = QVector3D(1,0,0);
	QVector3D backVec = QVector3D(0,0,1);

	//QMatrix4x4 matRot;
	//matRot.rotate(qRadiansToDegrees(m_Theta), frontVec);
	//matRot.rotate(qRadiansToDegrees(m_Phi), rightVec);
	////eyePos = matRot * backVec;
	//eyePos = backVec * matRot;
	//eyePos *= m_Radius;
	//eyePos += m_LookAtPt;

	//var viewWithPitchAndRoll = _view * Matrix4.CreateFromAxisAngle(_right, _pitchAngle) * Matrix4.CreateFromAxisAngle(_front, _rollAngle);
	//_viewWithOffset = Matrix4.LookAt(viewWithPitchAndRoll.Column2.Xyz * _cameraDistance + _pivot, _pivot, viewWithPitchAndRoll.Column1.Xyz);
	//_viewWithOffset *= Matrix4.CreateTranslation(_panVector);


	//QVector3D camFocusVec = eyePos - m_LookAtPt;
	//rightVec = QVector3D::crossProduct(camFocusVec, upVec);
	//upVec = QVector3D::crossProduct(rightVec, camFocusVec);
	//camFocusVec.normalize();
	////upVec = QVector3D::crossProduct(eyeToLookAt, leftVec);
	//QMatrix4x4 matRotAroundUp;
	//matRotAroundUp.rotate(qRadiansToDegrees(m_Theta), upVec);
	//QMatrix4x4 matRotAroundRight;
	//matRotAroundRight.rotate(qRadiansToDegrees(m_Theta), upVec);
	//camFocusVec = matRotAroundRight * matRotAroundUp * camFocusVec;
	//camFocusVec += m_LookAtPt;
	//m_MatView.lookAt(camFocusVec, m_LookAtPt, upVec);

	//QVector3D camFocusVec = eyePos - m_LookAtPt;
	//camFocusVec.normalize();
	//upVec = QVector3D::crossProduct(camFocusVec, leftVec);

	//QVector3D viewVec(cos(m_Theta)*cos(m_Phi),
	//				  sin(m_Phi),
	//				  -sin(m_Theta)*cos(m_Phi));
	//eyePos = m_LookAtPt - (m_Radius*viewVec);
	//upVec = QVector3D(0,1,0);
	//rightVec = QVector3D::crossProduct(viewVec, upVec);
	//upVec = QVector3D::crossProduct(viewVec, rightVec);
	//QQuaternion viewQua(0, viewVec);

	//rightVec = QVector3D::crossProduct(eyePos, upVec);
	//upVec = QVector3D::crossProduct(eyePos, rightVec);

	m_ViewMat.lookAt(m_EyePos, m_LookAtPt, upVec);

	return m_ViewMat;
}

QMatrix4x4 &OrbitalCamera::getProjMat()
{
	return m_ProjMat;
}

void OrbitalCamera::rotateTheta(float radians)
{
	m_Theta += radians;
	while (m_Theta > 2*M_PIf)
		m_Theta -= 2*M_PIf;
	while (m_Theta < 0)
		m_Theta += 2*M_PIf;
}

void OrbitalCamera::rotatePhi(float radians)
{
	m_Phi += radians;
	while (m_Phi > 2*M_PIf)
		m_Phi -= 2*M_PIf;
	while (m_Phi < 0)
		m_Phi += 2*M_PIf;
}

void OrbitalCamera::increaseRadius(float delta)
{
	m_Radius += delta;
	if (m_Radius < MIN_RADIUS)
		m_Radius = MIN_RADIUS;
}

void OrbitalCamera::setProj(QMatrix4x4 proj)
{
	m_ProjMat = proj;
}

void GLWidget::mousePressEvent(QMouseEvent *event)
{
	if (m_Entities.isEmpty())
		return;
	m_LastPos = event->pos();
}

void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
	if (m_Entities.isEmpty())
		return;

	int dx = event->x() - m_LastPos.x();
	int dy = event->y() - m_LastPos.y();

	if (event->buttons() & Qt::LeftButton)
	{
		// rotate

		// percentage of screen
		float dxPercent =  ((float)dx) / (float)(this->width());
		float dyPercent = -((float)dy) / (float)(this->height());

		// full rotation is 2*PI
		float dxFloat = dxPercent * 2*M_PIf;
		float dyFloat = dyPercent * 2*M_PIf;

		//m_OrbCam.rotateTheta(dxFloat);
		//m_OrbCam.rotatePhi(dyFloat);
		//m_Entities.last()->RotateY(dxFloat);
		m_Entities.last()->RotateZ(-dxFloat);
		m_Entities.last()->RotateX(-dyFloat);
		this->update();
	}
	else if (event->buttons() & Qt::RightButton)
	{
		//TODO: pan based on view, projection, world position of lookAt
		//      https://stackoverflow.com/questions/12097693/3d-scene-panning-in-perspective-projection-opengl
		//      http://www.codeproject.com/Articles/43005/Perfect-Panning-in-D-Zoom-Too
		//TODO: alternate mode translates object (instead of camera)
		// pan

		// percentage of screen
		float dxPercent =  ((float)dx) / (float)(this->width());
		float dyPercent = -((float)dy) / (float)(this->height());

		// translate amount
		const float SCALE = 30.0f;
		float dxFloat = dxPercent * SCALE;
		float dyFloat = dyPercent * SCALE;

		m_OrbCam.translateLookAtPt(QVector3D(dxFloat, dyFloat, 0));
		this->update();
	}
	m_LastPos = event->pos();
}

void GLWidget::wheelEvent(QWheelEvent *event)
{
	if (m_Entities.isEmpty())
		return;
	float deltaFloat = -(float)(event->delta()) / 30.0f;
	m_OrbCam.increaseRadius(deltaFloat);
	this->update();
}

void GLWidget::keyPressEventHandler(QKeyEvent *event)
{
	//return;
	//if (event->isAutoRepeat())
	//	return;
	switch (event->key())
	{
	case Qt::Key_A:
		m_OrbCam.rotateTheta(M_PIf/16.0f);
		this->update();
		break;
	case Qt::Key_D:
		m_OrbCam.rotateTheta(-M_PIf/16.0f);
		this->update();
		break;
	//case Qt::Key_W:
	//	m_OrbCam.rotatePhi(M_PIf/16.0f);
	//	this->update();
	//	break;
	//case Qt::Key_S:
	//	m_OrbCam.rotatePhi(-M_PIf/16.0f);
	//	this->update();
	//	break;
	default:
		break;
	}
}

void GLWidget::keyReleaseEventHandler(QKeyEvent *event)
{
	//if (event->isAutoRepeat())
	//	return;
	switch (event->key())
	{
	case Qt::Key_W:
		//qDebug() << "W up";
		break;
	default:
		break;
	}
}

void GLWidget::initializeGL()
{
	for (QList<StlEntity*>::iterator it = m_Entities.begin(); it != m_Entities.end(); ++it)
	{
		(*it)->init();
	}

	qglClearColor(QColor(128, 128, 128));

	glClearDepth(1.0f);
	glDepthFunc(GL_LESS);
	glEnable(GL_DEPTH_TEST);

	glFrontFace(GL_CCW);
	glCullFace(GL_BACK);
	glEnable(GL_CULL_FACE);
}

void GLWidget::paintGL()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	//m_LightPos = m_OrbCam.getEyePos();
	for (QList<StlEntity*>::iterator it = m_Entities.begin(); it != m_Entities.end(); ++it)
	{
		(*it)->draw(m_OrbCam.getViewMat(), m_OrbCam.getProjMat(), m_LightPos);
	}
}

void GLWidget::resizeGL(int width, int height)
{
	glViewport(0, 0, width, height);

	QMatrix4x4 matProj;
	matProj.setToIdentity();
	float aspectRatio = (float)width / (float)(height != 0 ? height : 1);
	matProj.perspective(45.0f, aspectRatio, 0.01f, 500.0f);

	//if (!m_Entities.isEmpty())
	//{
	//	StlEntity *ent = m_Entities.first();
	//	matProj.setToIdentity();
	//	matProj.ortho(-ent->GetWidth()/2, ent->GetWidth()/2, -ent->GetHeight()/2, ent->GetHeight()/2, -ent->GetDepth()/2 - 30, ent->GetDepth()/2);
	//}

	m_OrbCam.setProj(matProj);
}

void GLWidget::addEntityFromFile(QString sFileName, float &surfaceArea, float &volume)
{
	//TODO: entity list goes here, create a new entity and add it to the list and load from file!!!
	StlEntity *newEnt = new StlEntity();
	newEnt->loadFromFile(sFileName);

	const float SPACING = 3.0f;
	if (!m_Entities.empty())
	{
		float fTrans = m_Entities.last()->GetWorldMaxX() + SPACING + newEnt->GetWidthScaled()/2.0f;
		// minX += (maxX * scale + offX) + 3 +
		//qDebug() << fTrans
		//		 << "(" << m_Entities.last()->GetWorldMaxX() << ", " << newEnt->GetWidthScaled()/2.0f << ")"
		//		 << " (" << m_Entities.last()->GetModelMaxX() << ", " << m_Entities.last()->GetOffX() << ", " << m_Entities.last()->GetScale() << ")";
		newEnt->TranslateX(fTrans);
	}
	//qDebug() << newEnt->GetModelMaxX() << ", " << newEnt->GetModelMaxXScaled() << ", " << newEnt->GetWorldMaxX() << ", " << newEnt->GetWidthScaled();

	m_Entities.append(newEnt);
	this->initializeGL();
	this->update();

	// surface area is scaled x2; for ex a 10x10 area scaled by 1.5 has a surface area of 10x10*1.5*1.5
	surfaceArea = newEnt->getSurfaceArea() * newEnt->GetScale() * newEnt->GetScale();

	// volume is scaled x3; for ex a 10x10x10 cube scaled by 1.5 has a volume of 15x15x15 = 10x10x10*1.5x1.5x1.5
	volume = newEnt->getVolume() * newEnt->GetScale() * newEnt->GetScale() * newEnt->GetScale();
}

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
	ui->setupUi(this);

	ui->textBrowser->insertPlainText("surface area\n");
	ui->textBrowser->insertPlainText("volume\n");
	ui->textBrowser->insertPlainText("---\n");

	// notes to sanity check calculations
	// All models are scaled to fit within 15x15x15 to 25x25x25 cubes
	// So max volume is 15625; there is no max surface area for concave shapes
	//
	// surface area of sphere is 4*PI*r^2, volume is (4/3)*PI*r^3
	// diameter of 25: 4*pi*(25/2)^2 = 1963.50, (4/3)*pi*(25/2)^3 = 8181.23
	// 300_polygon_sphere_100mm.STL:   1937.08,                     7962.69
	// 79600_polygon_sphere_100mm.STL: 1962.68,                     8175.42
	//
	//TODO: test some convex objects that I can also hand calculate?
}

MainWindow::~MainWindow()
{
	delete ui;
}

QString sTempFilePath = "../pemSTL/STL/";
void MainWindow::on_action_Open_triggered()
{
	QString sFileName = QFileDialog::getOpenFileName(
				this, tr("Open STL File (add object)"),
				sTempFilePath, tr("STL Files (*.stl)"));
	if (sFileName == "")
		return;
	float surfaceArea = 0.0f;
	float volume = 0.0f;
	ui->glWidget->addEntityFromFile(sFileName, surfaceArea, volume);
	ui->textBrowser->insertPlainText(QString::number(surfaceArea) + "\n");
	ui->textBrowser->insertPlainText(QString::number(volume) + "\n");
	ui->textBrowser->insertPlainText("---\n");
}

void MainWindow::on_actionE_xit_triggered()
{
	close();
}

void MainWindow::keyPressEvent(QKeyEvent *event)
{
	ui->glWidget->keyPressEventHandler(event);
}

void MainWindow::keyReleaseEvent(QKeyEvent *event)
{
	ui->glWidget->keyReleaseEventHandler(event);
}







